/*
 * Copyright (c) 2003-2010, Live Media Pty. Ltd.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted
 * provided that the following conditions are met:
 *
 *  1. Redistributions of source code must retain the above copyright notice, this list of
 *     conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright notice, this list of
 *     conditions and the following disclaimer in the documentation and/or other materials
 *     provided with the distribution.
 *  3. Neither the name of Live Media nor the names of its contributors may be used to endorse
 *     or promote products derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.argot.mcg;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.Iterator;

import com.argot.ReferenceTypeMap;
import com.argot.TypeElement;
import com.argot.TypeException;
import com.argot.TypeLibrary;
import com.argot.TypeLocation;
import com.argot.TypeOutputStream;
import com.argot.meta.MetaDefinition;
import com.argot.network.ProtocolTypeMapper;
import com.argot.remote.MetaInterface;
import com.argot.remote.MetaMethod;
import com.argot.remote.MetaObject;
import com.colony.container.simple.SimpleLocation;


public class ArgotSourceGenerator 
{
	private ReferenceTypeMap _map;
	private ReferenceTypeMap _locationMap;
	private MetaObject _base;
	private int _interfaceType;
	
	public ArgotSourceGenerator( TypeLibrary library, ReferenceTypeMap map, MetaObject base, int typeId ) 
	throws TypeException, IOException
	{
		_map = map;
		_locationMap = new ReferenceTypeMap( library, new ProtocolTypeMapper() );
		_locationMap.setReferenceMap(_map);
		_base = base;
		_interfaceType = typeId;
		
		// prime the map by writing the object reference.
		writeObject( _map.getLibrary(), _base );
		
		generateMap( _interfaceType );
	}
	
	
	private byte[] writeType( TypeLibrary library, int libraryid ) 
	throws TypeException, IOException
	{
		ByteArrayOutputStream baos1 = new ByteArrayOutputStream();
		TypeOutputStream out1 = new TypeOutputStream( baos1, _map );	
		MetaDefinition definition = (MetaDefinition) _map.getLibrary().getStructure(libraryid);
		
		out1.writeObject( "meta.definition", definition );
		baos1.close();
		
		return baos1.toByteArray();
	}
	
	private byte[] writeLocation( TypeLibrary library, int libraryid ) 
	throws TypeException, IOException
	{
		ByteArrayOutputStream baos1 = new ByteArrayOutputStream();
		TypeOutputStream out1 = new TypeOutputStream( baos1, _locationMap );	
		TypeLocation location = (TypeLocation) _locationMap.getLibrary().getLocation(libraryid);
		
		out1.writeObject( "dictionary.location", location );
		baos1.close();
		
		return baos1.toByteArray();
	}	
	
	private byte[] writeObject( TypeLibrary library, MetaObject obj ) 
	throws TypeException, IOException
	{
		ByteArrayOutputStream baos1 = new ByteArrayOutputStream();
		TypeOutputStream out1 = new TypeOutputStream( baos1, _map );	
		
		out1.writeObject( "remote.object", obj );	
		baos1.close();
		
		return baos1.toByteArray();
	}
	
	public void generateMap( int typeId ) 
	throws TypeException, IOException
	{
		int count = -1;
		int newcount = 0;
		
		writeType( _map.getLibrary(), typeId);
		
		TypeElement element = _map.getLibrary().getStructure( typeId );
		if ( element instanceof MetaInterface )
		{
			mapMethods( (MetaInterface) element );
		}
		
		// required type for get base object.
		_map.getStreamId("bool");
		
		// required type for rpc.
		_map.getStreamId("remote.rpc.request");
		_map.getStreamId("remote.rpc.response");
		
		while ( count != newcount )
		{
			count = newcount;
			
			Iterator i = _map.getIdList().iterator();
			
			while(i.hasNext())
			{
				Integer id = (Integer) i.next();
				
				writeType( _map.getLibrary(), _map.getDefinitionId( id.intValue()) );
			}
			newcount = _map.size();
		}
	}
	
	private void mapMethods(MetaInterface metaInterface) 
	throws TypeException, IOException 
	{
		Iterator iter = metaInterface.getMetaMethodIterator();
		while( iter.hasNext() )
		{
			MetaMethod metaMethod = (MetaMethod) iter.next();
			_map.getStreamId( metaMethod.getMemberTypeId() );
			writeType( _map.getLibrary(), metaMethod.getMemberTypeId() );
		}
		
	}

	public void generateOutput( OutputStream out ) 
	throws TypeException, IOException
	{
		PrintWriter writer = new PrintWriter( out );
		System.out.println("generate output");
		
		writer.write("/* Generated output */\n");
		writer.write("/* Argot MCG (Micro Code Generator) Version " + ArgotMicroCodeGenerator.VERSION +" */\n");
		writer.write("\n");
		writer.write("#include <inttypes.h>\n");
		writer.write("#include <stdbool.h>\n");
		writer.write("#include \"dictionary.h\"\n");
		writer.write("\n");
		writer.write( "type_library library = \n");
		writer.write( "{\n");
		writer.write( "    " + _map.size() + ",\n");
		writer.write( " {\n");

				
		Iterator<Integer> i = _map.getIdList().iterator();
		
		while(i.hasNext())
		{
			Integer id = (Integer) i.next();
			String name = _map.getName( id.intValue() ).getFullName();
			String namedef = name.replaceAll("\\.","_");
			namedef = namedef.toUpperCase();
			byte locationBytes[] = writeLocation( _map.getLibrary(), _map.getDefinitionId( id.intValue() ));
			byte definitionBytes[] = writeType( _map.getLibrary(), _map.getDefinitionId( id.intValue() ) );

			writer.write( "  { { " + locationBytes.length +", ");			
			writeHexString( writer, locationBytes);
			writer.write( " }, { " + definitionBytes.length + ", ");
			writeHexString( writer, definitionBytes );
		    writer.write( " } }");
		    if ( i.hasNext() )
		    	writer.write( ",\n");
		    
		}
		
		writer.write("\n} };\n");
		
		byte[] data = writeObject( _map.getLibrary(), _base );
		writer.write("\n");
		writer.write("type_object base_object = { ");
		writer.write( data.length + ", ");
		writeHexString( writer, data );
		writer.write("};\n\n");

		writer.flush();

	}
	
	private static char hexDigits[] = { '0', '1', '2','3','4','5','6','7','8','9','a','b','c','d','e','f' };
	
	private void writeHexString( PrintWriter writer, byte[] bytes )
	{
		writer.write("\"");
	    for (int x = 0; x < bytes.length; x++) 
	    {
	    	writer.write( "\\x");
	    	writer.write( hexDigits[ ((bytes[x] >>> 4) & 0x0F) ]);
	    	writer.write( hexDigits[ ((bytes[x]) & 0x0F) ]);
	    }
		writer.write("\"");	    
	}

}
